package com.byp;

/**
 * 目的是不限定年份，以支持更大范围的时间计算
 * 时间关系，暂不处理月份日期及闰年问题
 */

import org.springframework.util.Assert;

import java.util.Calendar;
import java.util.Objects;
import java.util.TimeZone;

public class DateBase implements java.io.Serializable, Cloneable, Comparable<DateBase>
{

    //此参数仅用于兼容java.util.Date,并便于实例化Date范围内的对象
    private transient long currentTime;

    private int year;

    //0-11 表示1-12月
    private int month;

    //1-31
    private int day;

    //0-23
    private int hour;

    //0-59
    private int minute;

    //0-59
    private int second;

    //0-999
    private int millSecond;

//    private TimeZone zone = TimeZone.getDefault();

    private final static int[] dayInit = {31,28,31,30,31,30,31,31,30,31,30,31};

    public DateBase() {
        this(System.currentTimeMillis());
    }

    public DateBase(long date) {
        currentTime = date;
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(date);
        this.year = calendar.get(Calendar.YEAR);
        this.month = calendar.get(Calendar.MONTH);
        this.day = calendar.get(Calendar.DAY_OF_MONTH);
        this.hour = calendar.get(Calendar.HOUR);
        this.minute = calendar.get(Calendar.MINUTE);
        this.second = calendar.get(Calendar.SECOND);
        this.millSecond = calendar.get(Calendar.MILLISECOND);
    }

    public DateBase(int year, int month, int date) {
        this(year, month, date, 0, 0, 0);
    }

    public DateBase(int year, int month, int date, int hrs, int min) {
        this(year, month, date, hrs, min, 0);
    }

    public DateBase(int year, int month, int date, int hrs, int min, int sec) {
        this(year, month, date, hrs, min, sec, 0);
    }

    /**
     * TODO 暂不处理日期/小时/分钟/秒/毫秒等有效值范围
     * @param year
     * @param month
     * @param date
     * @param hrs
     * @param min
     * @param sec
     * @param mills
     */
    public DateBase(int year, int month, int date, int hrs, int min, int sec, int mills) {
        this.year = year ;
        if (month >= 12) {
            this.year += month / 12;
            month %= 12;
        } else if (month < 0) {
            this.year += Math.floorDiv(month, 12);
            month = Math.floorMod(month, 12);
        }
        this.month = month;
        int comparedDay = dayInit[month] + 1;
        if(month == 1 && isLeapYear(year)) {
            comparedDay++;
        }
        Assert.isTrue(date > 0 && date < comparedDay, "暂不支持日期超范围数字");
        this.day = date;
        Assert.isTrue(hrs >= 0 && hrs < 60, "暂不支持小时超范围数字");
        this.hour = hrs;
        Assert.isTrue(min >= 0 && min < 60, "暂不支持分钟超范围数字");
        this.minute = min;
        Assert.isTrue(sec >= 0 && sec < 60, "暂不支持秒超范围数字");
        this.second = sec;
        Assert.isTrue(mills >= 0 && mills < 1000, "暂不支持毫秒超范围数字");
        this.millSecond = mills;
    }

    public int getYear() {
        return this.year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getMonth() {
        return this.month;
    }

    public void setMonth(int month) {
        if (month >= 12) {
            this.year += month / 12;
            month %= 12;
        } else if (month < 0) {
            this.year += Math.floorDiv(month, 12);
            month = Math.floorMod(month, 12);
        }
        this.month = month;
    }

    public int getDay() {
        return this.day;
    }

    public void setDay(int date) {
        this.day = date;
    }

    public int getHour() {
        return this.hour;
    }

    public void setHour(int hours) {
        this.hour = hours;
    }

    public int getMinute() {
        return this.minute;
    }

    public void setMinute(int minute) {
        this.minute = minute;
    }

    public int getSecond() {
        return this.second;
    }

    public void setSecond(int second) {
        this.second = second;
    }

    public int getMillSecond() {
        return millSecond;
    }

    public void setMillSecond(int millSecond) {
        this.millSecond = millSecond;
    }

    public boolean before(DateBase when) {
        if(this.year!= when.year) {
            return this.year < when.year;
        }
        if(this.month!= when.month) {
            return this.month < when.month;
        }
        if(this.day!= when.day) {
            return this.day < when.day;
        }
        if(this.hour!= when.hour) {
            return this.hour < when.hour;
        }
        if(this.minute!= when.minute) {
            return this.minute < when.minute;
        }
        if(this.second!= when.second) {
            return this.second < when.second;
        }
        if(this.millSecond!= when.millSecond) {
            return this.millSecond < when.millSecond;
        }
        return false;
    }

    public boolean after(DateBase when) {
        if(this.year!= when.year) {
            return this.year > when.year;
        }
        if(this.month!= when.month) {
            return this.month > when.month;
        }
        if(this.day!= when.day) {
            return this.day > when.day;
        }
        if(this.hour!= when.hour) {
            return this.hour > when.hour;
        }
        if(this.minute!= when.minute) {
            return this.minute > when.minute;
        }
        if(this.second!= when.second) {
            return this.second > when.second;
        }
        if(this.millSecond!= when.millSecond) {
            return this.millSecond > when.millSecond;
        }
        return false;
    }

    public static boolean isLeapYear(int year) {
        if(year % 100 == 0) return year % 400 == 0;
        return year % 4 == 0;
    }

    /**
     * 获得指定日期与当前天数差
      * @param dateBase
     * @return 相隔天数
     */
    public int getDiffDays( DateBase dateBase) {
        DateBase dateFrom = this;
        DateBase dateTo = dateBase;
        if(dateTo.before(dateFrom)) {
            dateTo = this;
            dateFrom = dateBase;
        }
        int fromYear = dateFrom.getYear();
        int toYear = dateTo.getYear();
        int dayNums = 0;
        if(toYear - fromYear > 1) {
            dayNums += calculateDays(fromYear+1, toYear-1);
        }
        if(toYear != fromYear) {
            dayNums += calculateLeftDaysInYear(dateFrom);
            dayNums += calculatePastDaysInYear(dateTo);
        } else {
            dayNums = dayNums + calculateLeftDaysInYear(dateFrom) - calculateLeftDaysInYear(dateTo);
        }
        return dayNums;
    }

    /**
     * 获得指定日期与当前小时差
     * @param dateBase
     * @return 相隔小时数
     */
    public int getDiffHours( DateBase dateBase) {
        DateBase dateFrom = this;
        DateBase dateTo = dateBase;
        if(dateTo.before(dateFrom)) {
            dateTo = this;
            dateFrom = dateBase;
        }
        int dayHours = getDiffDays(dateBase) * 24;
        // 减去起始时间已过去小时数
        dayHours = dayHours - dateFrom.getHour();
        // 加上结束当日有效小时数
        dayHours = dayHours + dateTo.getHour();

        return dayHours;
    }

    public static int calculatePastDaysInYear(DateBase dateBase) {
        int numDays = 0;
        for(int i=0; i< dateBase.month ; i++) {
            numDays += dayInit[i];
        }
        if(dateBase.month > 1 && isLeapYear(dateBase.year)) {
            numDays++;
        }
        numDays += dateBase.day;
        return numDays;
    }

    public static int calculateLeftDaysInYear(DateBase dateBase) {
        int numDays = 365;
        if(isLeapYear(dateBase.year)) {
            numDays = 366;
        }
        return numDays - calculatePastDaysInYear(dateBase);
    }

    private int calculateDays(int fromYear, int toYear) {
        int num = 0;
        for(int i = fromYear; i< toYear+1; i++) {
            num += (isLeapYear(i)?366:365);
        }
        return num;
    }

    public String getShowingString() {
        return getShowingString("yyyy-MM-dd HH:mm:ss", this);
    }

    public static String getShowingString(String pattern, DateBase dateBase) {
        String str = pattern;

        str = str.replace("yyyy", left0(dateBase.year, 4));
        str = str.replace("MM", left0(dateBase.month + 1, 2));
        str = str.replace("dd", left0(dateBase.day, 2));
        str = str.replace("HH", left0(dateBase.hour, 2));
        str = str.replace("mm", left0(dateBase.minute, 2));
        str = str.replace("ss", left0(dateBase.second, 2));

        return str;
    }

    public static String left0(int num, int len) {
        boolean negtive = false;
        if(num < 0) {
            num = Math.abs(num);
            negtive = true;
        }
        String str = "" + num;
        int l = str.length();
        for (int i = str.length(); i < len; i++) {
            str = "0" + str;
        }
        if(negtive) {
            str = "-" + str;
        }
        return str;
    }

    public int compareTo(DateBase anotherDate) {
        if(this.equals(anotherDate)) {
            return 0;
        }
        if(this.before(anotherDate)) return -1;
        return 1;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        DateBase dateBase = (DateBase) o;
        return year == dateBase.year && month == dateBase.month && day == dateBase.day && hour == dateBase.hour && minute == dateBase.minute && second == dateBase.second && millSecond == dateBase.millSecond;
    }

    @Override
    public int hashCode() {
        return Objects.hash(year, month, day, hour, minute, second, millSecond);
    }

    private void handleLastDay(boolean isLeap, int preMonth, int preDay, boolean afterIsLeap) {
        boolean isLastDay = (preMonth == 1 && ((isLeap && preDay == 29) || (!isLeap && preDay == 28)) )
                || (preMonth!=1 && (preDay == dayInit[preMonth]));
        if(isLastDay) {
            if (afterIsLeap && this.month == 1) {
                if (this.month == 1) {
                    this.day = afterIsLeap ? 29 : 28;
                } else {
                    this.day = dayInit[this.month];
                }
            }
        }
        if(this.day > dayInit[this.month]) {
            if(afterIsLeap && this.month == 1) {
                this.day = 29;
            } else {
                this.day = dayInit[this.month];
            }
        }
    }
    public void addYear(int change) {
        boolean isLeap = isLeapYear(this.year);
        this.year = this.year + change;
        handleLastDay(isLeap, this.month, this.day, isLeapYear(this.year));
    }

    public void addMonth(int change) {
        boolean isLeap = isLeapYear(this.year);
        int preMonth = this.month;
        int preDay = this.day;
        int afterMonth = this.month + change;
        if (afterMonth >= 12) {
            this.year += afterMonth / 12;
            afterMonth %= 12;
        } else if (afterMonth < 0) {
            this.year += Math.floorDiv(afterMonth, 12);
            afterMonth = Math.floorMod(afterMonth, 12);
        }
        this.month = afterMonth;
        handleLastDay(isLeap, preMonth, preDay, isLeapYear(this.year));
    }

    /**
     * TODO
     */
    public void addDay() {

    }
}
